#include "GRO_Painter.h"



namespace GRO
{
    
    PainterMap* globalPainterMap = new PainterMap;


    PainterMap* getPainterMap()
    {
        return globalPainterMap;
    }

    void setPainterMap(PainterMap* painterMap)
    {
        globalPainterMap = painterMap;
    }
    
    
    PainterMap::PainterMap()
    {
        //GRO::GradientRamp GRO::Default_TitleBarGradientRamp(0x000088, 0x000088);  // Win95-style
        //GRO::GradientRamp GRO::Default_TitleBarGradientRamp(0x000088, 0x6666aa);  // Win98-style
        //GRO::GradientRamp GRO::Default_TitleBarGradientRamp(5, 0x3388bb, 0x1155bb, 0.3f, 0x2277bb, 0.8f, 0x3388bb, 0.9f, 0x1155bb);  // WinXP/KDE3-style

        Default_DarkShadowBoxPainter = add(new BoxPainter(0x888888, 5, 5));
        Default_DarkBoxPainter = add(new BoxPainter(0x888888, 0, 0));
        Default_LightBoxPainter = add(new BoxPainter(0x999999, 0, 0));
        
        //GRO::LinearGradientBoxPainter GRO::Default_TitleBarBoxPainter(DOWN, &Default_TitleBarGradientRamp);

        //GRO::BoxPainter GRO::Default_ProgressPainter(0x4444bb, 0, 0);
        //GRO::BevelFramePainter GRO::Default_ProgressFramePainter(1, 0, 0);

        Default_ButtonUpPainter = add(new BevelFramePainter);
        Default_ButtonDownPainter = add(new BevelFramePainter(2, 1, 1));
        Default_GroupPainter = add(new BevelFramePainter(1, 0, 1));

        Default_CheckDesignPainter = add(new CheckDesignPainter);
        //GRO::XDesignPainter GRO::Default_XDesignPainter;
        //GRO::ResizeDesignPainter GRO::Default_ResizeDesignPainter;

        Default_UpArrowDesignPainter = add(new ArrowheadDesignPainter(UP, 3, 0x000000));
        Default_DownArrowDesignPainter = add(new ArrowheadDesignPainter(DOWN, 3, 0x000000));
        //GRO::ArrowheadDesignPainter GRO::Default_LeftArrowDesignPainter(LEFT, 3, 0x000000);
        //GRO::ArrowheadDesignPainter GRO::Default_RightArrowDesignPainter(RIGHT, 3, 0x000000);
    }
    
    PainterMap::~PainterMap()
    {
        for(std::map<GRO_BoxPainterID, BoxPainter*>::iterator e = boxPainters.begin(); e != boxPainters.end(); e++)
            delete e->second;
        
        for(std::map<GRO_FramePainterID, FramePainter*>::iterator e =  framePainters.begin(); e != framePainters.end(); e++)
            delete e->second;
        
        for(std::map<GRO_DesignPainterID, DesignPainter*>::iterator e =  designPainters.begin(); e != designPainters.end(); e++)
            delete e->second;
        
    }


    GradientRamp::GradientRamp(Uint16 numColors, Uint32 FirstColor, Uint32 SecondColor, float SecondPos, Uint32 ThirdColor, ...)
        : deleteMe(true)
        , first(0, FirstColor)
    {
        controls.push_back(GradientPoint(SecondPos, SecondColor));
        Uint32 color = ThirdColor;
        float pos = 1.0f;
        bool gotColor = 1;
        va_list lst;
        va_start(lst, ThirdColor);
        for(int i = 6; i < 2*numColors; i++)
        {
            if(!gotColor)
            {
                color = va_arg(lst, Uint32);
                gotColor = 1;
            }
            else
            {
                pos = va_arg(lst, double);
                controls.push_back(GradientPoint(pos, color));
                gotColor = 0;
            }
        }
        va_end(lst);
        last.color = color;
        last.pos = 1.0f;
    }


        void GradientRamp::set(Uint16 numColors, Uint32 FirstColor, Uint32 SecondColor, float SecondPos, Uint32 ThirdColor, ...)
        {
            first.color = FirstColor;
            first.alpha = SDL_ALPHA_OPAQUE;
            controls.clear();
            controls.push_back(GradientPoint(SecondPos, SecondColor));
            Uint32 color = ThirdColor;
            float pos = 1.0f;
            bool gotColor = 1;
            va_list lst;
            va_start(lst, ThirdColor);
            for(int i = 6; i < 2*numColors; i++)
            {
                if(!gotColor)
                {
                    color = va_arg(lst, Uint32);
                    gotColor = 1;
                }
                else
                {
                    pos = va_arg(lst, double);
                    controls.push_back(GradientPoint(pos, color));
                    gotColor = 0;
                }
            }
            va_end(lst);
            last.color = color;
            last.pos = 1.0f;
        }

    
        void GradientRamp::setAlpha(Uint16 numColors, Uint32 FirstColor, Uint8 FirstAlpha, Uint32 SecondColor, Uint8 SecondAlpha, float SecondPos, Uint32 ThirdColor, Uint8 ThirdAlpha, ...)
        {
            first.color = FirstColor;
            first.alpha = FirstAlpha;
            controls.clear();
            controls.push_back(GradientPoint(SecondPos, SecondColor, SecondAlpha));
            Uint32 color = ThirdColor;
            Uint8 alpha = ThirdAlpha;
            float pos = 1.0f;
            bool gotColor = 1;
            bool gotAlpha = 1;
            va_list lst;
            va_start(lst, ThirdAlpha);
            for(int i = 8; i < 2*numColors; i++)
            {
                if(!gotColor)
                {
                    color = va_arg(lst, Uint32);
                    gotColor = 1;
                }
                else if(!gotAlpha)
                {
                    alpha = va_arg(lst, int);//Uint8);
                    gotAlpha = 1;
                }
                else
                {
                    pos = va_arg(lst, double);
                    controls.push_back(GradientPoint(pos, color, alpha));
                    gotColor = 0;
                    gotAlpha = 0;
                }
            }
            va_end(lst);
            last.color = color;
            last.alpha = alpha;
            last.pos = 1.0f;
        }
        
        
        void GradientRamp::remove(int index)
        {
            if(index >= 0 && (unsigned int)index < controls.size())
            {
                for(std::list<GradientPoint>::iterator e = controls.begin(); e != controls.end(); ++e, --index)
                {
                    if(index == 0)
                    {
                        controls.erase(e);
                        break;
                    }
                }
            }
        }
        
        
        void GradientRamp::remove(float from, float to)
        {
            for(std::list<GradientPoint>::iterator e = controls.begin(); e != controls.end();)
            {
                if(e->pos >= from && e->pos <= to)
                {
                    e = controls.erase(e);
                    continue;
                }
                ++e;
            }
        }
        
        
        void GradientRamp::reverse()
        {
            // Invert positions
            for(std::list<GradientPoint>::iterator e = controls.begin(); e != controls.end(); ++e)
            {
                e->pos = 1 - e->pos;
            }
            // Swap first and last
            Uint32 c = last.color;
            Uint8 a = last.alpha;
            last.color = first.color;
            last.alpha = first.alpha;
            first.color = c;
            first.alpha = a;
            // Resort
            controls.sort(compare_GradientPoint);
            //controls.reverse();
        }

        
        
        
        
        // FIXME: This should probably use the renderer somehow
        void BoxPainter::pushClipRect(Sint16 x, Sint16 y)
        {
            /*if(currentRenderer == NULL)
                return;
            SDL_Rect r = currentRenderer->GetClip();
            r.w += 2*x;
            r.h += 2*y;
            r.x -= x;
            r.y -= y;
            if(r.x < 0)
                r.x = 0;
            if(r.y < 0)
                r.y = 0;
            
            Gui& gui = Gui::instance();
            
            if(r.x + r.w > gui.area.w)
                r.w = gui.area.w - r.x;
            if(r.y + r.h > gui.area.h)
                r.h = gui.area.h - r.y;
            currentRenderer->SetClip(r);*/
        }
        
        
        void BoxPainter::drawDropShadow(const SDL_Rect& rect, Renderer* currentRenderer)
        {
            if(currentRenderer == NULL)
                return;
            if (!(dropShadowX == 0 && dropShadowY == 0) && dropShadowAlpha != SDL_ALPHA_TRANSPARENT)
            {
                // Push clip rect so that the shadow will be drawn...
                // Is this cheating?
                if(!clip)
                {
                    pushClipRect(abs(dropShadowX), abs(dropShadowY));
                }
                if (boxAlpha == SDL_ALPHA_OPAQUE) // save processing by only drawing the necessary rects
                {
                    // Two rects: 4 coord pairs
                    // Rect 'a' is the horizontal stripe
                    Sint16 x1a, y1a, x2a, y2a, x1b = 0, y1b = 0, x2b = 0, y2b = 0;
                    if (dropShadowY > 0)
                    {
                        y1a = rect.y + rect.h;
                        y2a = y1a - 1 + dropShadowY;
                        y1b = rect.y + dropShadowY;
                        y2b = rect.y + rect.h - 1;
                        if (y2a > y1a + rect.h - 1)
                        {
                            y1a = y2a - (rect.h - 1);

                        }
                    }
                    else if (dropShadowY < 0)
                    {
                        y1a = rect.y + dropShadowY - 1;
                        y2a = rect.y - 1;
                        y1b = rect.y;
                        y2b = rect.y + rect.h - 1 + dropShadowY;
                        if (y1a + rect.h - 1 < rect.y)
                            y2a = y1a + rect.h - 1;
                    }
                    else
                    {
                        y1a = rect.y;
                        y2a = rect.y + rect.h - 1;
                    }

                    if (dropShadowX > 0)
                    {
                        x1b = rect.x + rect.w;
                        x2b = x1b - 1 + dropShadowX;
                        if (x2b > x1b + rect.w - 1)
                            x1b = x2b - (rect.w - 1);
                        x1a = rect.x + dropShadowX;
                        x2a = rect.x + rect.w - 1 + dropShadowX;
                    }
                    else if (dropShadowX < 0)
                    {
                        x1b = rect.x + dropShadowX;
                        x2b = rect.x - 1;
                        if (x1b + rect.w - 1 < rect.x)
                            x2b = x1b + rect.w - 1;
                        x1a = rect.x + dropShadowX;
                        x2a = rect.x + rect.w - 1 + dropShadowX;
                    }
                    else
                    {
                        x1a = rect.x;
                        x2a = rect.x + rect.w - 1;
                    }

                    // So far, it works right until the shadow leaves the widget's area
                    // and it doesn't work right for shadowx == 0 or shadowy == 0

                    if (dropShadowAlpha == SDL_ALPHA_OPAQUE)
                    {
                        currentRenderer->RectFilled(x1a, y1a, x2a, y2a, dropShadowColor);
                        if (dropShadowX != 0 || dropShadowY != 0)
                            currentRenderer->RectFilled(x1b, y1b, x2b, y2b, dropShadowColor);
                    }
                    else
                    {
                        currentRenderer->RectFilledBlend(x1a, y1a, x2a, y2a, dropShadowColor, dropShadowAlpha);
                        if (dropShadowX != 0 || dropShadowY != 0)
                            currentRenderer->RectFilledBlend(x1b, y1b, x2b, y2b, dropShadowColor, dropShadowAlpha);
                    }
                }
                else // Draw full rect.  For transparent boxes, the whole shadow should still show.
                {
                    if (dropShadowAlpha == SDL_ALPHA_OPAQUE)
                        currentRenderer->RectFilled(rect.x + dropShadowX, rect.y + dropShadowY, rect.x + rect.w - 1 + dropShadowX, rect.y + rect.h - 1 + dropShadowY, dropShadowColor);
                    else
                        currentRenderer->RectFilledBlend(rect.x + dropShadowX, rect.y + dropShadowY, rect.x + rect.w - 1 + dropShadowX, rect.y + rect.h - 1 + dropShadowY, dropShadowColor, dropShadowAlpha);
                }
            }
        }
        
    #ifndef GRO_NO_DEFAULT_PAINTERS
        
        /*void LinearGradientBoxPainter::draw(const SDL_Rect& rect, Renderer* currentRenderer)
        {
            if(currentRenderer == NULL)
                return;
            if(gradient == NULL)
                return;
                
            Uint32 c1, c2;
            float pos1, pos2;
            c1 = gradient->first.color;
            pos1 = gradient->first.pos;
            for(list<GradientPoint>::iterator e = gradient->controls.begin(); e != gradient->controls.end(); ++e)
            {
                c2 = e->color;
                pos2 = e->pos;
                drawGradient(rect, c1, pos1, c2, pos2);
                c1 = c2;
                pos1 = pos2;
            }
            c2 = gradient->last.color;
            pos2 = gradient->last.pos;
            drawGradient(rect, c1, pos1, c2, pos2);
        }*/
        
        
        /*void ImageBoxPainter::drawBoxImage(SDL_Rect& rect)
        {
            if(currentRenderer == NULL)
                return;
            // If image doesn't fill the whole space, we need a background.
            if(image == NULL)
                return;
            SDL_Rect src = {sourceX, sourceY, rect.w, rect.h};
            SDL_Rect dest = rect;
            dest.x += offsetX;
            dest.y += offsetY;
            
            currentRenderer->Blit(image, &src, &dest);
        }*/
        
    
    #endif //GRO_NO_DEFAULT_PAINTERS
    
    GRO_BoxPainterID PainterMap::add(BoxPainter* painter)
    {
        if(painter == NULL)
            return GRO_BoxPainterID(0);
        
        GRO_BoxPainterID id(boxPainters.size()+1);
        boxPainters.insert(std::make_pair(id, painter));
        
        return id;
    }
    GRO_FramePainterID PainterMap::add(FramePainter* painter)
    {
        if(painter == NULL)
            return GRO_FramePainterID(0);
        
        GRO_FramePainterID id(framePainters.size()+1);
        framePainters.insert(std::make_pair(id, painter));
        
        return id;
    }
    GRO_DesignPainterID PainterMap::add(DesignPainter* painter)
    {
        if(painter == NULL)
            return GRO_DesignPainterID(0);
        
        GRO_DesignPainterID id(designPainters.size()+1);
        designPainters.insert(std::make_pair(id, painter));
        
        return id;
    }

    BoxPainter* PainterMap::get(const GRO_BoxPainterID& id)
    {
        if(id.getValue() == 0)
            return NULL;
        return boxPainters[id];
    }
    FramePainter* PainterMap::get(const GRO_FramePainterID& id)
    {
        if(id.getValue() == 0)
            return NULL;
        return framePainters[id];
    }
    DesignPainter* PainterMap::get(const GRO_DesignPainterID& id)
    {
        if(id.getValue() == 0)
            return NULL;
        return designPainters[id];
    }

    BoxPainter* PainterMap::remove(const GRO_BoxPainterID& id)
    {
        if(id.getValue() == 0)
            return NULL;
        BoxPainter* p = boxPainters[id];
        boxPainters.erase(id);
        
        // FIXME: Check against defaults?
        
        return p;
    }
    FramePainter* PainterMap::remove(const GRO_FramePainterID& id)
    {
        if(id.getValue() == 0)
            return NULL;
        FramePainter* p = framePainters[id];
        framePainters.erase(id);
        
        // FIXME: Check against defaults?
        
        return p;
    }
    DesignPainter* PainterMap::remove(const GRO_DesignPainterID& id)
    {
        if(id.getValue() == 0)
            return NULL;
        DesignPainter* p = designPainters[id];
        designPainters.erase(id);
        
        // FIXME: Check against defaults?
        
        return p;
    }
    
    
    void GRO_BoxPainterID::draw(const SDL_Rect& rect, Renderer* renderer)
    {
        if(getPainterMap() == NULL)
            return;
            
        BoxPainter* b = getPainterMap()->get(*this);
        if(b != NULL)
            b->draw(rect, renderer);
    }
    
    
    void GRO_FramePainterID::draw(const SDL_Rect& rect, Renderer* renderer)
    {
        if(getPainterMap() == NULL)
            return;
            
        FramePainter* b = getPainterMap()->get(*this);
        if(b != NULL)
            b->draw(rect, renderer);
    }
    
    void GRO_DesignPainterID::drawDesign(const SDL_Rect& rect, Rect* designPush, Renderer* renderer)
    {
        if(getPainterMap() == NULL)
            return;
            
        DesignPainter* b = getPainterMap()->get(*this);
        if(b != NULL)
            b->drawDesign(rect, designPush, renderer);
    }
    
}


